package com.supermap.services.security.ext.gzca;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import javax.inject.Singleton;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.ClientProtocolException;
import org.apache.shiro.web.servlet.NameableFilter;
import org.apache.shiro.web.util.WebUtils;
import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.servlet.ServletProperties;
import org.slf4j.cal10n.LocLogger;

import com.google.common.base.Preconditions;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
import com.supermap.iportal.web.commontypes.IportalUser;
import com.supermap.iportal.web.components.IportalSecurityComponent;
import com.supermap.iportal.web.database.IportalDatabaseContextBuilder;
import com.supermap.server.config.IportalSetting;
import com.supermap.services.security.BuiltInToken;
import com.supermap.services.security.Manager;
import com.supermap.services.security.SecurityConstants;
import com.supermap.services.security.SecurityInfoDAO;
import com.supermap.services.security.SecurityUtility;
import com.supermap.services.security.ShiroUtil;
import com.supermap.services.util.LogUtil;
import com.supermap.services.util.ProductTypeUtil;
import com.supermap.services.util.ProductTypeUtil.ProductType;
import com.supermap.services.util.ResourceManager;

import gzca.dbo.utils.BCSM2Util;

public class GZCAOAuth2Filter extends NameableFilter {
    private static final String FLAG_CONFIG = "/config";
    private static final String FLAG_CODE = "/code";
    private String authorizeEndpoint;
    private String tokenEndpoint;
    private String platCode;
    private String secretKey;
    private String userInfoEndpoint;
    private List<AutoCloseable> closeOnDestroy = new ArrayList<>();
    private BCSM2Util util = new BCSM2Util();
    private static final  String servletPath = "/gzca";
    private static LocLogger logger = LogUtil.getLocLogger(GZCAOAuth2Filter.class, ResourceManager.getCommontypesResource());
    private AtomicInitializer<Filter> initializer = new AtomicInitializer<Filter>();
    private Client client;
    private WebTarget tokenTarget;
    private WebTarget userTarget;
    private static final String GZCA = "GZCA";
    private static final GenericType<Envelop<PostTokenResult>> TOKEN_TYPE = new GenericType<Envelop<PostTokenResult>>(){};
    private static final GenericType<Envelop<PostUserResult>> USER_TYPE = new GenericType<Envelop<PostUserResult>>(){};
    
    @Override
    public void destroy() {
        try {
            closeOnDestroy.forEach(toClose -> {
                try {
                    toClose.close();
                } catch (Exception e) {
                    logger.warn("", e);
                }
            });
        } finally {
            super.destroy();
        }
    }
    
    public void setClient(Client value) {
        client = value;
    }
    

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequestWrapper httpRequest = new HttpServletRequestWrapper(WebUtils.toHttp(request)){
            public String getServletPath() {
                return servletPath;
            };
        };
        initializer.get(() -> initialize(request.getServletContext())).doFilter(httpRequest, response, chain);
    }

    private Filter initialize(ServletContext servletContext) {
        if (client == null) {
            ClientConfig clientConfig = new ClientConfig().connectorProvider(new ApacheConnectorProvider());
            clientConfig.register(new JacksonJsonProvider());
            client = ClientBuilder.newClient(clientConfig);
        }
        
        tokenTarget = client.target(tokenEndpoint);
        userTarget = client.target(userInfoEndpoint);
        
        ResourceConfig resourceConfig = new ResourceConfig().register(new Resource()).register(new JacksonJsonProvider());
        ServletContainer servletContainer = new ServletContainer(resourceConfig);
        try {
            servletContainer.init(new ServletConfig() {

                @Override
                public ServletContext getServletContext() {
                    return servletContext;
                }

                @Override
                public Enumeration<String> getInitParameterNames() {
                    Iterator<String> origin = Iterators.forEnumeration(servletContext.getInitParameterNames());
                    List<String> list = Lists.newArrayList(origin);
                    list.add(ServletProperties.FILTER_CONTEXT_PATH);
                    return Iterators.asEnumeration(list.iterator());
                }

                @Override
                public String getInitParameter(String name) {
                    return ServletProperties.FILTER_CONTEXT_PATH.equals(name) ? servletPath : servletContext.getInitParameter(name);
                }
                @Override
                public String getServletName() {
                    return servletContext.getServletContextName();
                }
            });
        } catch (ServletException | RuntimeException e) {
            return new Filter() {
                
                @Override
                public void init(FilterConfig filterConfig) throws ServletException {
                    // TODO Auto-generated method stub
                    
                }
                
                @Override
                public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
                    throw e;
                }
                
                @Override
                public void destroy() {
                    // TODO Auto-generated method stub
                    
                }
            };
        }
        closeOnDestroy.add(() -> servletContainer.destroy());
        return servletContainer;
    }



    public void setAuthorizeEndpoint(String authorizeEndpoint) {
        this.authorizeEndpoint = authorizeEndpoint;
    }

    public void setTokenEndpoint(String tokenEndpoint) {
        this.tokenEndpoint = tokenEndpoint;
        
    }

    public void setPlatCode(String platCode) {
        this.platCode = platCode;
    }

    public void setSecretKey(String secretKey) {
        this.secretKey = secretKey;
    }
    
    @Path("/gzca")
    @Singleton
    public class Resource {

        @GET
        @Produces(MediaType.TEXT_HTML)
        public String page() throws IOException {
            try(InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("ZHTYOAuth2GZCA.html")) {
                return IOUtils.toString(is, StandardCharsets.UTF_8);
            }
        }
        @Path(FLAG_CONFIG)
        @GET
        @Produces(MediaType.APPLICATION_JSON)
        public Response redirect(@Context HttpServletRequest httpRequest) throws MalformedURLException, UnsupportedEncodingException, URISyntaxException {
            String response = String.format(Locale.ENGLISH, "{\"url\":\"%s\",\"option\":{\"platCode\":\"%s\",\"state\":\"222\"}}", authorizeEndpoint, platCode);
            return Response.ok(response, MediaType.APPLICATION_JSON_TYPE).build();
        }
        
        @Path(FLAG_CODE)
        @POST
        public Response login(@Context ServletContext conext, @Context HttpServletRequest request, @Context HttpServletResponse response, String authCode) throws ClientProtocolException, IOException {

            String sign = util.sign(secretKey, "authorization_code" + authCode + platCode);
            PostTokenParam postTokenParam = new PostTokenParam();
            postTokenParam.authCode = authCode;
            postTokenParam.platCode = platCode;
            postTokenParam.sign = sign;
            Response postTokenRes = tokenTarget.request().buildPost(Entity.entity(postTokenParam, MediaType.APPLICATION_JSON)).invoke();
            Envelop<PostTokenResult> postTokenResult = postTokenRes.readEntity(TOKEN_TYPE);
            String token = ensureSucces(postTokenResult).accessToken;
            PostUserParam postUserParam = new PostUserParam();
            postUserParam.accessToken = token;
            PostUserResult postUserResult = ensureSucces(userTarget.request().post(Entity.entity(postUserParam, MediaType.APPLICATION_JSON)).readEntity(USER_TYPE));


            SecurityInfoDAO dao = SecurityUtility.getOAuth2UserDAO(conext);
            String userName = dao.getUserNameByOpenID(postUserResult.uid, GZCA);
            if(StringUtils.isEmpty(userName)) {
                IportalUser user = new IportalUser();
                userName = GZCA + postUserResult.uid;
                user.name = userName;
                user.nickname = postUserResult.realName;
                user.password = RandomStringUtils.random(2, "abcdefghijklmnopqrstuvwxyz") + RandomStringUtils.random(8, "0123456789");
                if (ProductType.iPortal.equals(ProductTypeUtil.getProductType())) {
                    IportalSetting iportalSetting = Manager.getInstance().getIportalSetting();
                    if (SecurityConstants.ROLE_PORTAL_USER .equals(iportalSetting.registerSetting.defaultRole) && iportalSetting.registerSetting.allowDataCenterRole) {
                        user.roles = new String[] { SecurityConstants.ROLE_PORTAL_USER, SecurityConstants.ROLE_DATA_CENTER};
                    } else {
                        user.roles = new String[] { iportalSetting.registerSetting.defaultRole};
                    }
                }
                user.userGroups = new String[]{SecurityConstants.GROUP_THIRD_PART_AUTHORIZED};

                Manager.getInstance().addUser(user, postUserResult.uid, GZCA);
                IportalSecurityComponent iportalSecurityComponent = IportalDatabaseContextBuilder.getInstance().getIportalDatabaseContext().getBeanFactory().getBean("iportalSecurityComponent", IportalSecurityComponent.class);
                iportalSecurityComponent.updateUserInfo(user);
            }
            ShiroUtil.login(request, response, new BuiltInToken(userName).lookupRole(true).lookupPermission(true).save(true));
            return Response.ok(request.getContextPath(), MediaType.TEXT_PLAIN_TYPE).build();
        }
        private <T> T ensureSucces(Envelop<T> envelop) {
            Preconditions.checkState(envelop.success);
            return envelop.data;
        }
    }
    
    public void setUserInfoEndpoint(String userInfoEndpoint) {
        this.userInfoEndpoint = userInfoEndpoint;
    }

}
